package main

import (
	"flag"
	"fmt"
	"gitee.com/konica/WangkaMianliu/wangkautil"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"net"
	"os"
	"os/exec"
	"text/template"
	"time"
	"unsafe"
)

type (
	// QData QData
	QData struct {
		IP     string
		QGUID  string
		QToken string
	}

	// ConfigTemplate tiny 配置模板
	ConfigTemplate struct {
		TinyRunUID int
		Port       int
		QData
	}
)

var (
	// Device 网络设备
	Device string
	// OutPath 输出路径
	OutPath string
	// Port 本地监听端口
	Port int
	// TinyRunUID Tiny uid
	TinyRunUID int
	// ConfType 配置类型
	ConfType string
)

func init() {
	flag.StringVar(&Device, "dev", "rmnet0", "device")
	flag.StringVar(&OutPath, "out", "", "out path")
	flag.IntVar(&Port, "port", 65080, "tiny listen port")
	flag.IntVar(&TinyRunUID, "uid", 0, "tiny run uid")
	flag.StringVar(&ConfType, "type", "tiny", "print config type")
	flag.Parse()
}

func main() {
	// 启用数据
	enableData()

	// snaplen捕获消息大小, promisc是否以混合模式打开
	handle, err := pcap.OpenLive(Device, 1600, true, pcap.BlockForever)
	if err != nil {
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	err = handle.SetBPFFilter("port 8090 or port 8091")
	if err != nil {
		fmt.Println(err)
		return
	}

	source := gopacket.NewPacketSource(handle, handle.LinkType())

	// 清空缓存
	err = wangkautil.ClearQQBrowserTokenCache()
	if err != nil {
		fmt.Printf("ClearQQBrowserTokenCache error: %s\n", err)
	}

	// 启动QQ浏览器
	launchQQBrowser()
	for packet := range source.Packets() {
		//判断数据包是否是Payload
		if payload := packet.Layer(gopacket.LayerTypePayload); payload != nil {
			layerContents := payload.LayerContents()

			var ipAddr *net.IP
			if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil {
				ip, ok := ipLayer.(*layers.IPv4)
				if !ok {
					continue
				}

				ipAddr = &ip.DstIP
			}

			qguids := QGUIDExp.FindSubmatch(layerContents)
			if len(qguids) < 2 {
				continue
			}

			qtokens := QTokenExp.FindSubmatch(layerContents)
			if len(qtokens) < 2 {
				continue
			}

			printConfig(*(*string)(unsafe.Pointer(&qguids[1])), *(*string)(unsafe.Pointer(&qtokens[1])), ipAddr.String())

			// 停止QQ浏览器
			stopQQBrowser()
			break
		}
	}
}

// enableData 启用数据
func enableData() {
	enableCMD := exec.Command("su", "-cn", "u:r:system_app:s0", "-c", "svc data enable")
	enableCMD.Stderr = os.Stderr
	err := enableCMD.Run()
	if err != nil {
		fmt.Printf("enableDataError: %s\n", err)
		return
	}
	time.Sleep(1e9)
}

// launchQQBrowser 启动QQ浏览器
func launchQQBrowser() {
	launchCMD := exec.Command("su", "-cn", "u:r:system_app:s0", "-c", "am", "start", "-n", "com.tencent.mtt/com.tencent.mtt.MainActivity")
	err := launchCMD.Run()
	if err != nil {
		fmt.Printf("LaunchQQBrowserError: %s\n", err)
		return
	}
}

// stopQQBrowser 停止QQ浏览器
func stopQQBrowser() {
	stopCMD := exec.Command("su", "-cn", "u:r:system_app:s0", "-c", "am", "force-stop", "com.tencent.mtt")
	err := stopCMD.Run()
	if err != nil {
		fmt.Printf("StopQQBrowserError: %s\n", err)
		return
	}
}

func printConfig(qguid, qtoken, ipAddr string) {
	var confTmpl string
	switch ConfType {
	case "tiny":
		confTmpl = `listen_port={{.Port}};
worker_proc=1;
daemon=on;
uid={{.TinyRunUID}};
mode=wap;

http_ip={{.IP}};
http_port=8090;
http_first="[M] http://[H][U] [V]\r\nQ-GUID:{{.QGUID}}\r\nQ-Token:{{.QToken}}\r\nQ-Type: picture\r\nConnection: Keep-Alive\r\n";

https_connect=on;
https_ip={{.IP}};
https_port=8091;
https_first="[M] [H] [V]\r\nProxy-Connection: keep-alive\r\nQ-GUID:{{.QGUID}}\r\nQ-Token:{{.QToken}}\r\nQ-Type: picture\r\n";

dns_tcp=http;
dns_listen_port=65053;
dns_url="119.29.29.29";
`
	case "tcp_over_http_proxy":
		confTmpl = `LocalAddr="0.0.0.0:{{.Port}}";
DestAddr="{{.IP}}:8091";
Headers="Q-GUID:{{.QGUID}}\r\nQ-Token:{{.QToken}}\r\nQ-Type: picture\r\n";
RelayMethod="POST";
`
	}

	data := ConfigTemplate{
		TinyRunUID: TinyRunUID,
		Port:       Port,
		QData: QData{
			IP:     ipAddr,
			QGUID:  qguid,
			QToken: qtoken,
		},
	}

	tmpl, err := template.New("conf").Parse(confTmpl)
	checkErr(err)

	out := os.Stdout
	if OutPath != "" {
		out, err = os.OpenFile(OutPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
		checkErr(err)
		defer out.Close()
	}
	err = tmpl.Execute(out, &data)
	checkErr(err)

	if OutPath != "" {
		fmt.Printf("%s: up-to-date\n", OutPath)
	}
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}
